﻿using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.Net.Sockets;
using System.ComponentModel;
using VmcRemote.Common.Interfaces;

namespace VmcRemote.Common
{
    public class Remote : IDisposable
    {
        private const int CommandTimeout = 2;

        private class IssuedCommand
        {
            public RemoteCommand Command;
            public long TicksIssues;
        }

        private Queue<IssuedCommand> commandQueue;
        private System.Threading.AutoResetEvent autoReset = new System.Threading.AutoResetEvent(false);
        private System.Threading.Thread executeThread;
        private static object queueLock = new object();

        private IConnection connection;
        private IMediaCenter server;

        public event EventHandler<RemoteErrorEventArgs> ErrorOccurred;
        protected virtual void OnErrorOccurred(Exception ex)
        {
            if (ErrorOccurred != null)
                ErrorOccurred(this, new RemoteErrorEventArgs(ex));
        }

        public Remote(IConnection connection, IMediaCenter server)
        {
            if (connection == null) throw new ArgumentNullException("connection");
            if (server == null) throw new ArgumentNullException("server");

            this.connection = connection;
            this.server = server;

            commandQueue = new Queue<IssuedCommand>();
            executeThread = new System.Threading.Thread(ExecuteThreadStart);
            executeThread.Start();
        }

        public void ExecuteCommand(RemoteCommand command)
        {
            // Add a command to the queue and signal the waiting thread.
            lock (queueLock)
            {
                commandQueue.Enqueue(new IssuedCommand { Command = command, TicksIssues = DateTime.Now.Ticks });
            }
            autoReset.Set();
        }

        /// <summary>
        /// Used as a ThreadStart to manage threaded execution of commands.
        /// </summary>
        private void ExecuteThreadStart()
        {
            while (true)
            {
                try
                {
                    // If there are no commands in the queue, wait for one to be added.
                    if (commandQueue.Count == 0)
                    {
                        autoReset.WaitOne();
                        autoReset.Reset();
                    }
                    // If the command hasn't timed-out, then execute it.
                    IssuedCommand command;
                    lock (queueLock)
                    {
                        command = commandQueue.Dequeue();
                    }
                    if (DateTime.Now.Ticks < TimeSpan.FromSeconds(CommandTimeout).Ticks + command.TicksIssues)
                        ExecuteCommandInternal(command.Command);
                }
                catch (Exception ex)
                {
                    OnErrorOccurred(ex);
                }
            }
        }

        /// <summary>
        /// Internal implementation of command's execution.
        /// </summary>
        /// <param name="command"></param>
        private void ExecuteCommandInternal(RemoteCommand command)
        {
            try
            {
                // Use the connection to execute the command by passing it the raw text provided by the server.
                connection.SendCommand(server.GetCommandString(command));
            }
            catch (Exception ex)
            {
                OnErrorOccurred(ex);
            }
        }

        private static string GetCommandString(RemoteCommand command)
        {
            Type type = command.GetType();
            MemberInfo[] memInfo = type.GetMember(command.ToString());
            if (memInfo != null && memInfo.Length > 0)
            {
                object[] attrs = memInfo[0].GetCustomAttributes(typeof(CommandAttribute), false);
                if (attrs != null && attrs.Length > 0)
                    return ((CommandAttribute)attrs[0]).Name;
            }
            return null;
        }

        #region IDisposable Members

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        private void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (connection != null)
                    connection.Dispose();
                if (executeThread != null)
                    executeThread.Abort();
                if (autoReset != null)
                    autoReset.Close();
            }
        }

        ~Remote()
        {
            Dispose(false);
        }

        #endregion
    }
}
